﻿using Caliburn.Micro;
using MusicExplorer.Common;
using MusicExplorer.Data;
using MusicExplorer.Model;
using MusicExplorer.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml.Data;
using MusicExplorer.Extensions;

namespace MusicExplorer.ViewModels.Wrappers
{
    public sealed class EntitiesCollection : BindableCollection<EntityResultViewModel>, ISupportIncrementalLoading
    {
        private const int ENTITIES_TO_ADD = 10;
        private const int MAX_CONCURRENCY = 10;

        private sealed class Query
        {
            private readonly List<Entity> m_entities;
            private readonly RepositoryBase m_repository;
            private readonly string m_terms;

            private int m_currentPage;

            public Query(RepositoryBase repo, string terms)
            {
                m_entities = new List<Entity>();
                m_repository = repo;
                m_currentPage = m_repository.StartingPage;
                m_terms = terms;
                m_hasMoreItem = true;
            }

            public async Task LoadNextEntitiesAsync(EntityType type, CancellationToken token)
            {
                try
                {
                    token.ThrowIfCancellationRequested();
                    var entities = await m_repository.SearchEntitiesAsync(type, m_terms, m_currentPage, token);
                    token.ThrowIfCancellationRequested();
                    if (entities == null || (entities != null && entities.Count == 0))
                    {
                        m_hasMoreItem = false;
                    }
                    else
                    {
                        m_entities.AddRange(entities);
                        m_currentPage++;
                    }
                }
                catch (Exception ex)
                {
                    m_hasMoreItem = false;
                }
            }

            private bool m_hasMoreItem;
            public bool HasMoreItems { get { return m_hasMoreItem; } }
            public bool LoadingNeeded { get { return m_entities.Count < ENTITIES_TO_ADD && m_hasMoreItem; } }
            public List<Entity> Entities { get { return m_entities; } }
            public RepositoryBase Repository { get { return m_repository; } }
        }


        private readonly QueryDefinition m_queryDefinition;
        private readonly List<RepositoryBase> m_repositories;
        private readonly List<Query> m_queries;
        private readonly string m_terms;
        private readonly CancellationToken m_token;
        private readonly GroupEditionViewModel m_parent;
        private readonly IEnumerable<Entity> m_selectedEntities;
        private readonly CoreWindow m_window;
        
        private bool m_firstTime;

        public EntitiesCollection(GroupEditionViewModel parent, QueryDefinition queryDefinition, string terms,
            IEnumerable<Entity> selectedEntities, CancellationToken token)
        {
            m_queries = new List<Query>();
            m_queryDefinition = queryDefinition;
            m_firstTime = true;
            m_terms = terms;
            m_token = token;
            m_parent = parent;
            m_repositories = new List<RepositoryBase>();
            m_selectedEntities = selectedEntities;
            m_window = CoreWindow.GetForCurrentThread();
        }

        private void InitEntities()
        {
            var repositories = RepositoryBase.GetAllRepositories();
            m_repositories.AddRange(repositories.Select(r => RepositoryBase.GetInstance(r)));
            foreach (var repo in m_repositories.Where(r => r.SupportQuery(m_queryDefinition)))
            {
                m_queries.Add(new Query(repo, m_terms));
            }
        }

        private bool LoadingNeeded { get { return m_queries.Exists(q => q.LoadingNeeded); } }
        public bool HasMoreItems
        {
            get
            {
                var hasMoreItems =  m_firstTime || m_queries.Exists(q => q.HasMoreItems);
                return hasMoreItems;
            }
        }

        public event EventHandler StartSearching = delegate { };
        public event EventHandler EndSearching = delegate { };
        public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            StartSearching(this, EventArgs.Empty);

            return Task.Run<LoadMoreItemsResult>(async () =>
                {
                    if (Parameters.UseSampleData)
                    {
                        await m_window.Dispatcher.ExecuteInUIThread(async () =>
                        {
                            m_firstTime = false;
                            for (int i = 0; i < 50; i++)
                            {
                                var entity = Entity.New(m_queryDefinition.EntityType, i.ToString(), "entity " + i, RepositoryBase.GetRepository(new Guid("C0C5501F-D815-41AF-A3CB-A8163D5D8E5A")));
                                Add(await EntityResultViewModel.CreateAsync(m_parent, entity, Count - 1));
                            }
                        });
                        return new LoadMoreItemsResult()
                        {
                            Count = 50
                        };
                    }

                    if (m_firstTime)
                    {
                        InitEntities();
                        m_firstTime = false;
                    }

                    do
                    {
                        var entitiesToLoad = m_queries.Where(e => e.LoadingNeeded).ToArray();
                        int loop = entitiesToLoad.Length / MAX_CONCURRENCY + 1;
                        for (int i = 0; i < loop; i ++)
                        {
                            int max = i * MAX_CONCURRENCY + MAX_CONCURRENCY;
                            int size = MAX_CONCURRENCY;
                            if (entitiesToLoad.Length < max)
                                size = entitiesToLoad.Length - (max - MAX_CONCURRENCY);

                            Task[] tasks = new Task[size];
                            for (int j = 0; j < size; j++)
                            {
                                if (entitiesToLoad.Length <= j) continue;

                                var entity = entitiesToLoad[j + i * MAX_CONCURRENCY];
                                tasks[j] = Task.Run(() => entity.LoadNextEntitiesAsync(m_queryDefinition.EntityType, m_token));
                            }

                            try
                            {
                                Task.WaitAll(tasks);
                            }
                            catch(Exception ex) { }

                            // Remove of selected entities
                            foreach (var entity in m_selectedEntities)
                            {
                                var queryToDelete = m_queries.FirstOrDefault(q => q.Entities.Exists(e => e.Id == entity.Id && e.Repository.Id == entity.Repository.Id));
                                if(queryToDelete != null)
                                {
                                    var entityToDelete = queryToDelete.Entities.First(e => e.Id == entity.Id && e.Repository.Id == entity.Repository.Id);
                                    queryToDelete.Entities.Remove(entityToDelete);
                                }
                            }

                            // Remove on unsupported entities
                            foreach(var query in m_queries)
                            {
                                if (query.Repository.UnsupportedEntities != null && query.Repository.UnsupportedEntities.ContainsKey(m_queryDefinition))
                                {
                                    var unsupportedEntities = query.Repository.UnsupportedEntities[m_queryDefinition];
                                    query.Entities.RemoveAll(e => unsupportedEntities.Contains(e.Id));
                                }
                            }
                        }
                    } while (LoadingNeeded);

                    List<Entity> entitiesToAdd;
                    switch(m_queryDefinition.EntityType)
                    {
                        case EntityType.Youtube:
                        case EntityType.Soundcloud:
                        case EntityType.Artist:
                        case EntityType.Label:
                            entitiesToAdd = m_queries.SelectMany(t => t.Entities).OrderBy(e => e.Position).Take(ENTITIES_TO_ADD).ToList();
                            break;
                        case EntityType.Genre:
                        case EntityType.WebSite:
                            entitiesToAdd = m_queries.SelectMany(t => t.Entities).OrderBy(t => t.Name).Take(ENTITIES_TO_ADD).ToList();
                            break;
                        default:
                            throw new Exception("Unsupported entity type");
                    }

                    await m_window.Dispatcher.ExecuteInUIThread(async () =>
                        {
                            foreach (var entity in entitiesToAdd)
                            {
                                var entityToDeleteIn = m_queries.First(e => e.Entities.Any(t => t.Id == entity.Id && t.Repository.Id == entity.Repository.Id));
                                entityToDeleteIn.Entities.Remove(entity);
                                var closure = entity;
                                var wrapper = await EntityResultViewModel.CreateAsync(m_parent, closure, Count - 1);
                                this.Add(wrapper);
                            }
                        });

                    await m_window.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => EndSearching(this, EventArgs.Empty));
                    return new LoadMoreItemsResult()
                    {
                        Count = ENTITIES_TO_ADD
                    };
                }).AsAsyncOperation<LoadMoreItemsResult>();
            
        }
    }
}
